/* Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#include "sql_parse.h"                       // check_access
#include "sql_table.h"                       // mysql_alter_table,
                                             // mysql_exchange_partition
#include "sql_base.h"                        // open_temporary_tables
#include "sql_alter.h"


Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root)
  :drop_list(rhs.drop_list, mem_root),
  alter_list(rhs.alter_list, mem_root),
  key_list(rhs.key_list, mem_root),
  create_list(rhs.create_list, mem_root),
  flags(rhs.flags),
  keys_onoff(rhs.keys_onoff),
  partition_names(rhs.partition_names, mem_root),
  num_parts(rhs.num_parts),
  requested_algorithm(rhs.requested_algorithm),
  requested_lock(rhs.requested_lock)
{
  /*
    Make deep copies of used objects.
    This is not a fully deep copy - clone() implementations
    of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec
    do not copy string constants. At the same length the only
    reason we make a copy currently is that ALTER/CREATE TABLE
    code changes input Alter_info definitions, but string
    constants never change.
  */
  list_copy_and_replace_each_value(drop_list, mem_root);
  list_copy_and_replace_each_value(alter_list, mem_root);
  list_copy_and_replace_each_value(key_list, mem_root);
  list_copy_and_replace_each_value(create_list, mem_root);
  /* partition_names are not deeply copied currently */
}


bool Alter_info::set_requested_algorithm(const LEX_STRING *str)
{
  // To avoid adding new keywords to the grammar, we match strings here.
  if (!my_strcasecmp(system_charset_info, str->str, "INPLACE"))
    requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE;
  else if (!my_strcasecmp(system_charset_info, str->str, "COPY"))
    requested_algorithm= ALTER_TABLE_ALGORITHM_COPY;
  else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
    requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT;
  else
    return true;
  return false;
}


bool Alter_info::set_requested_lock(const LEX_STRING *str)
{
  // To avoid adding new keywords to the grammar, we match strings here.
  if (!my_strcasecmp(system_charset_info, str->str, "NONE"))
    requested_lock= ALTER_TABLE_LOCK_NONE;
  else if (!my_strcasecmp(system_charset_info, str->str, "SHARED"))
    requested_lock= ALTER_TABLE_LOCK_SHARED;
  else if (!my_strcasecmp(system_charset_info, str->str, "EXCLUSIVE"))
    requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE;
  else if (!my_strcasecmp(system_charset_info, str->str, "DEFAULT"))
    requested_lock= ALTER_TABLE_LOCK_DEFAULT;
  else
    return true;
  return false;
}


Alter_table_ctx::Alter_table_ctx()
  : datetime_field(NULL), error_if_not_empty(false),
    tables_opened(0),
    db(NULL), table_name(NULL), alias(NULL),
    new_db(NULL), new_name(NULL), new_alias(NULL),
    fk_error_if_delete_row(false), fk_error_id(NULL),
    fk_error_table(NULL)
#ifndef DBUG_OFF
    , tmp_table(false)
#endif
{
}


Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list,
                                 uint tables_opened_arg,
                                 char *new_db_arg, char *new_name_arg)
  : datetime_field(NULL), error_if_not_empty(false),
    tables_opened(tables_opened_arg),
    new_db(new_db_arg), new_name(new_name_arg),
    fk_error_if_delete_row(false), fk_error_id(NULL),
    fk_error_table(NULL)
#ifndef DBUG_OFF
    , tmp_table(false)
#endif
{
  /*
    Assign members db, table_name, new_db and new_name
    to simplify further comparisions: we want to see if it's a RENAME
    later just by comparing the pointers, avoiding the need for strcmp.
  */
  db= table_list->db;
  table_name= table_list->table_name;
  alias= (lower_case_table_names == 2) ? table_list->alias : table_name;

  if (!new_db || !my_strcasecmp(table_alias_charset, new_db, db))
    new_db= db;

  if (new_name)
  {
    DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db, new_name));

    if (lower_case_table_names == 1) // Convert new_name/new_alias to lower case
    {
      my_casedn_str(files_charset_info, new_name);
      new_alias= new_name;
    }
    else if (lower_case_table_names == 2) // Convert new_name to lower case
    {
      strmov(new_alias= new_alias_buff, new_name);
      my_casedn_str(files_charset_info, new_name);
    }
    else
      new_alias= new_name; // LCTN=0 => case sensitive + case preserving

    if (!is_database_changed() &&
        !my_strcasecmp(table_alias_charset, new_name, table_name))
    {
      /*
        Source and destination table names are equal:
        make is_table_renamed() more efficient.
      */
      new_alias= table_name;
      new_name= table_name;
    }
  }
  else
  {
    new_alias= alias;
    new_name= table_name;
  }

  my_snprintf(tmp_name, sizeof(tmp_name), "%s-%lx_%lx", tmp_file_prefix,
              current_pid, thd->thread_id);
  /* Safety fix for InnoDB */
  if (lower_case_table_names)
    my_casedn_str(files_charset_info, tmp_name);

  if (table_list->table->s->tmp_table == NO_TMP_TABLE)
  {
    build_table_filename(path, sizeof(path) - 1, db, table_name, "", 0);

    build_table_filename(new_path, sizeof(new_path) - 1, new_db, new_name, "", 0);

    build_table_filename(new_filename, sizeof(new_filename) - 1,
                         new_db, new_name, reg_ext, 0);

    build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db, tmp_name, "",
                         FN_IS_TMP);
  }
  else
  {
    /*
      We are not filling path, new_path and new_filename members if
      we are altering temporary table as these members are not used in
      this case. This fact is enforced with assert.
    */
    build_tmptable_filename(thd, tmp_path, sizeof(tmp_path));
#ifndef DBUG_OFF
    tmp_table= true;
#endif
  }
}


bool Sql_cmd_alter_table::execute(THD *thd)
{
  LEX *lex= thd->lex;
  /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
  SELECT_LEX *select_lex= &lex->select_lex;
  /* first table of first SELECT_LEX */
  TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first;
  /*
    Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
    so we have to use a copy of this structure to make execution
    prepared statement- safe. A shallow copy is enough as no memory
    referenced from this structure will be modified.
    @todo move these into constructor...
  */
  HA_CREATE_INFO create_info(lex->create_info);
  Alter_info alter_info(lex->alter_info, thd->mem_root);
  ulong priv=0;
  ulong priv_needed= ALTER_ACL;
  bool result;

  DBUG_ENTER("Sql_cmd_alter_table::execute");

  if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
    DBUG_RETURN(TRUE);
  /*
    We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well
    as for RENAME TO, as being done by SQLCOM_RENAME_TABLE
  */
  if (alter_info.flags & (Alter_info::ALTER_DROP_PARTITION |
                          Alter_info::ALTER_RENAME))
    priv_needed|= DROP_ACL;

  /* Must be set in the parser */
  DBUG_ASSERT(select_lex->db);
  DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION));
  DBUG_ASSERT(!(alter_info.flags & Alter_info::ALTER_ADMIN_PARTITION));

//   if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
//     DBUG_RETURN(TRUE);                  /* purecov: inspected */

  if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL))
  {
    // Rename of table
    TABLE_LIST tmp_table;
    memset(&tmp_table, 0, sizeof(tmp_table));
    tmp_table.table_name= lex->name.str;
    tmp_table.db= select_lex->db;
    tmp_table.grant.privilege= priv;
//     if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE,
//                     UINT_MAX, FALSE))
//       DBUG_RETURN(TRUE);                  /* purecov: inspected */
  }

  /* Don't yet allow changing of symlinks with ALTER TABLE */
  if (create_info.data_file_name)
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                        WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
                        "DATA DIRECTORY");
  if (create_info.index_file_name)
    push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
                        WARN_OPTION_IGNORED, ER(WARN_OPTION_IGNORED),
                        "INDEX DIRECTORY");
  create_info.data_file_name= create_info.index_file_name= NULL;

  thd->enable_slow_log= opt_log_slow_admin_statements;

  result= mysql_alter_table(thd, select_lex->db, lex->name.str,
                            &create_info,
                            first_table,
                            &alter_info,
                            select_lex->order_list.elements,
                            select_lex->order_list.first,
                            lex->ignore);

  DBUG_RETURN(result);
}


bool Sql_cmd_discard_import_tablespace::execute(THD *thd)
{
  /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
  SELECT_LEX *select_lex= &thd->lex->select_lex;
  /* first table of first SELECT_LEX */
  TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first;

  thd->enable_slow_log= opt_log_slow_admin_statements;

  /*
    Check if we attempt to alter mysql.slow_log or
    mysql.general_log table and return an error if
    it is the case.
    TODO: this design is obsolete and will be removed.
  */
  int table_kind= check_if_log_table(table_list->db_length, table_list->db,
                                     table_list->table_name_length,
                                     table_list->table_name, false);

//   if (table_kind)
//   {
//     /* Disable alter of enabled log tables */
//     if (logger.is_log_table_enabled(table_kind))
//     {
//       my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
//       return true;
//     }
//   }

  /*
    Add current database to the list of accessed databases
    for this statement. Needed for MTS.
  */
//   thd->add_to_binlog_accessed_dbs(table_list->db);

  return
    mysql_discard_or_import_tablespace(thd, table_list,
                                       m_tablespace_op == DISCARD_TABLESPACE);
}
